package labs.second;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static labs.utilities.PrintUtilities.*;
import labs.utilities.LabsFileReader;
import labs.utilities.LabsFileWriter;

public class Parser {
	private final String inputFileName;
	private final String outputFileName;
	private labs.utilities.LabsFileReader reader;
	private labs.utilities.LabsFileWriter writer;
	private ArrayList<String> tokens;
	private HashMap<String, String> table;
	private ArrayList<String> keywords;
	private int nLines = 0;

	public Parser(String fileName) {
		this.inputFileName = fileName;
		this.outputFileName = formatOutputFile();
		tokens = new ArrayList<String>();
		sinitializeTabl();
	}

	private void sinitializeTabl() {
		table = new HashMap<String, String>();
		table.put(".", "DO");
		table.put("{", "LC");
		table.put("}", "RC");
		table.put("(", "LB");
		table.put(")", "RB");
		table.put("=", "AO");
		table.put(";", "SM");
		table.put("-", "MO");
		table.put("+", "PO");
		table.put("<", "LT");
		table.put(">", "GT");
		table.put("+", "PO");
		table.put("*", "MB");
		table.put("/", "DB");
		table.put(",", "FA");
		table.put("[]", "AA");
		table.put("[", "LS");
		table.put("]", "RS");
		table.put("+=", "RS");
		table.put("-=", "RS");
		table.put("*=", "RS");
		table.put("/*", "RS");
		table.put("<=", "LE");
		table.put(">=", "GE");
		table.put("==", "EQ");
		table.put("!", "LN");
		table.put("++", "PP");
		table.put("--", "MM");
		table.put("!=", "NE");
		table.put("&", "LA");
		table.put("|", "LO");
		table.put("^", "LX");
		String[] temp = new String[] { "abstract", "continue", "for", "new",
				"switch", "assert", "default", "goto", "package",
				"synchronized", "boolean", "do", "if", "private", "this",
				"break", "double", "implements", "protected", "throw", "byte",
				"else", "import", "public", "throws", "case", "enum",
				"instanceof", "return", "transient", "catch", "extends", "int",
				"short", "try", "char", "final", "interface", "static", "void",
				"class", "finally", "long", "strictfp", "volatile", "const",
				"float", "native", "super", "while", "true", "false", "System",
				"out", "println", "String" };
		keywords = new ArrayList<String>();
		for (int i = 0; i < temp.length; i++) {
			keywords.add(temp[i]);
		}
	}

	private String formatOutputFile() {
		int extensionIndex = inputFileName.lastIndexOf('.');
		int fileNameIndex = inputFileName.lastIndexOf('\\') + 1;
		return String.format("%s%sOut-generated%s",
				inputFileName.substring(0, fileNameIndex),
				inputFileName.substring(fileNameIndex, extensionIndex),
				inputFileName.substring(extensionIndex));
	}

	private Pattern buildRegexPattern() {
		// String combinedOperators = "(<=)|(>=)|(!=)|(==)";
		String TWO_OPERANDS_OPERATORS = "([+*/<>&^|!-])";
		String SINGLE_OPERANDS_OPERATORS = "((?:\\+\\+)|(?:--)|(?:<=)|(?:>=)|(?:==)|(?:!=))";
		String ASSIGNMENT_OPERATOR = "((?:=)|(?:\\*=)|(?:\\+=)|(?:/=)|(?:-=))";
		String BRACKETS_SEPARATORS = "((?:\\[\\])|[\\{\\}\\(\\)\\[\\];,:?.])";
		String IdentifierRegex = "([a-zA-Z_](?:\\w)*)";
		String NumbersRegex = "(\\d+(?:\\.\\d+)?(?:E\\d+)?)";
		String SingleLineComment = "(//[^\n\r]*)";
		String MultipleLinesComment = "(/[*](?:.|[\n\r])*?[*]/)";
		String CharRegEx = "((?:'.')|(?:'\\\\.'))";
		String StringRegex = "(\"(?:(?:.)|(?:\\\"))*?\")";

		String regex = String.format("%s|%s|%s|%s|%s|%s|%s|%s|%s|%s",
				SingleLineComment, MultipleLinesComment, CharRegEx,
				StringRegex, IdentifierRegex, NumbersRegex,
				SINGLE_OPERANDS_OPERATORS, TWO_OPERANDS_OPERATORS,
				ASSIGNMENT_OPERATOR, BRACKETS_SEPARATORS);
		Pattern tokensPattern = Pattern.compile(regex);
		return tokensPattern;
	}

	private void parse(Pattern tokensPattern, String wholeFile) {

		Matcher matcher = tokensPattern.matcher(wholeFile);
		while (matcher.find()) {
			String match = "";
			if ((match = matcher.group(1)) != null) {

			} else if ((match = matcher.group(2)) != null) {

			} else if ((match = matcher.group(3)) != null) {
				tokens.add(String.format("CH\t%s", match));
			} else if ((match = matcher.group(4)) != null) {
				tokens.add(String.format("ST\t%s", match));
			} else if ((match = matcher.group(5)) != null) {
				if (keywords.contains(match)) {
					tokens.add(String.format("KW\t%s", match));
				} else {
					tokens.add(String.format("ID\t%s", match));
				}
			} else if ((match = matcher.group(6)) != null) {
				tokens.add(String.format("NM\t%s", match));
			} else if ((match = matcher.group(7)) != null) {
				tokens.add(String.format("%s\t%s", table.get(match), match));
			} else if ((match = matcher.group(8)) != null) {
				tokens.add(String.format("%s\t%s", table.get(match), match));
			} else if ((match = matcher.group(9)) != null) {
				tokens.add(String.format("%s\t%s", table.get(match), match));
			} else if ((match = matcher.group(10)) != null) {
				tokens.add(String.format("%s\t%s", table.get(match), match));
			}
		}

	}

	private void writeOutput() {
		writer = new LabsFileWriter(outputFileName);
		writer.writeLines(tokens.toArray(new String[tokens.size()]));
		writer.close();
	}

	private StringBuffer readInput() {
		String[] lines = reader.readLines(true);
		nLines = lines.length;
		StringBuffer buffer = new StringBuffer();
		for (int i = 0; i < lines.length; i++) {
			buffer.append(lines[i]);
		}
		return buffer;
	}

	public void start() {
		try {
			printInfo("Start Parsing File: '%s'", inputFileName);
			reader = new LabsFileReader(inputFileName);
			StringBuffer buffer = readInput();
			printInfo("%s lines are read", nLines);
			if (reader.isOpened()) {
				Pattern tokensPattern = buildRegexPattern();
				reader.close();
				printInfo("Start parsing process . . .");
				parse(tokensPattern, buffer.toString());
				printInfo("%s tokens are extracted", tokens.size());
				writeOutput();
				printInfo("Output is written to File: '%s'", outputFileName);
			}
			printInfo("End Parsing File: '%s'", inputFileName);
		} catch (Exception e) {
			printError("Some error at lab-2");
			printException(e);
		}
	}

	public String[] getCompleteCommands(String[] lines) {
		ArrayList<String> commands = new ArrayList<String>(lines.length);
		Pattern commandEndPattern = Pattern.compile(".*?(?:(?:;)|(?:\\}))");
		// boolean isCompleteCommand = false;
		String completeCommand = "";
		for (String line : lines) {
			Matcher matcher = commandEndPattern.matcher(line);
			completeCommand += line;
			if (matcher.matches()) {
				// isCompleteCommand = true;
				commands.add(completeCommand);
				completeCommand = "";
			} else {
				completeCommand += " ";
			}
		}
		return commands.toArray(new String[commands.size()]);
	}

	public ArrayList<String> getTokens() {
		return this.tokens;
	}
}
